import { addChecksum, int2ByteArray } from "./util";

const NLF = [
    [
        [
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ],
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ]
        ],
        [
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ],
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ]
        ]
    ],
    [
        [
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ],
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ]
        ],
        [
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ],
            [
                [
                    [],
                    []
                ],
                [
                    [],
                    []
                ]
            ]
        ]
    ]
]


NLF[0][0][0][0][0]=0;
NLF[0][0][0][0][1]=1;
NLF[0][0][0][1][0]=1;
NLF[0][0][0][1][1]=1;
NLF[0][0][1][0][0]=0;
NLF[0][0][1][0][1]=1;
NLF[0][0][1][1][0]=0;
NLF[0][0][1][1][1]=0;

NLF[0][1][0][0][0]=0;
NLF[0][1][0][0][1]=0;
NLF[0][1][0][1][0]=1;
NLF[0][1][0][1][1]=0;
NLF[0][1][1][0][0]=1;
NLF[0][1][1][0][1]=1;
NLF[0][1][1][1][0]=1;
NLF[0][1][1][1][1]=0;

NLF[1][0][0][0][0]=0;
NLF[1][0][0][0][1]=0;
NLF[1][0][0][1][0]=1;
NLF[1][0][0][1][1]=1;
NLF[1][0][1][0][0]=1;
NLF[1][0][1][0][1]=0;
NLF[1][0][1][1][0]=1;
NLF[1][0][1][1][1]=0;

NLF[1][1][0][0][0]=0;
NLF[1][1][0][0][1]=1;
NLF[1][1][0][1][0]=0;
NLF[1][1][0][1][1]=1;
NLF[1][1][1][0][0]=1;
NLF[1][1][1][0][1]=1;
NLF[1][1][1][1][0]=0;
NLF[1][1][1][1][1]=0;


//获取source第n个位数
function getBit(source,n) {
    const temp1 = source & (1 << n);
    return temp1 != 0 ? 1 : 0;
}

//获取array第n个位数
function getArrayBit(keyArray,n) {
    const bit = keyArray[Math.floor(n / 8)] & (1 << (n % 8))//偏移量 = n % 8;   下标 = Math.floor(n / 8);
    return bit != 0 ? 1 : 0;
}

//source带进位右移
function RRC(source,c) {
    if (c != 0){
        source = (source >> 1) | 0x80000000;
    }else {
        source = (source >> 1) & 0x7fffffff;
    }
    return source;
}

//source带进位左移
function RLC(source,c) {
    if (c != 0){
        source = (source << 1) | 0x01;
    }else {
        source = (source << 1) & 0xFFFFFFFE;
    }
    return source;
}

/**
 * 用8byte的key做密钥进行加解密
 * 这里key为8个字符密钥，处理成8byte
 * @param {*} key 
 */
function saltKeyArray(key) {
    const salt = [0xA1, 0x12, 0x92, 0x32, 0x62, 0x99, 0x86, 0x08];//加解密双方约定盐值
    if (typeof key !== "string") {
        throw new Error("key为8个字符密钥");
    }
    const ka = Array.from(key); 
    while(ka.length < 8) { //不足8个字符前面补0 (padStart(8, '0'))
        ka.unshift('0') 
    }
    return salt.map((s, i) => ka[i].charCodeAt(0) ^ s);//转ASCII码并异或
}

/*
    source进行加密
    用8byte的key做密钥进行加密
 */
const ENCRYPT = (source, key) => {
    const ska = saltKeyArray(key);
    for (let i = 0;i < 528;i ++){
        const nlf = NLF[getBit(source, 31)][getBit(source, 26)][getBit(source, 20)][getBit(source, 9)][getBit(source, 1)];
        const y16 = getBit(source, 16);
        const y0 = getBit(source, 0);
        const index = i % 64;
        const k = getArrayBit(ska, index);
        const result = nlf ^ y16 ^ y0 ^ k;
        const c = result != 0 ? 1 : 0;
        source = RRC(source, c);
    }
    if (source < 0) {
        source = source >>> 0
    }
    return source + 3e9;
}

/*
    source进行解密
    用8byte的key做密钥进行加密
 */
const DECRYPT = (source, key) => {
    source -=3e9;
    source <<= 0;
    const ska = saltKeyArray(key);
    for (let i = 528;i > 0; i--){
        const nlf = NLF[getBit(source, 30)][getBit(source, 25)][getBit(source, 19)][getBit(source, 8)][getBit(source, 0)];
        const y15 = getBit(source, 15);
        const y31 = getBit(source, 31);
        const index = (i-1) % 64;
        const k = getArrayBit(ska, index);
        const result = nlf ^ y15 ^ y31 ^ k;
        const c = result != 0 ? 1 : 0;
        source = RLC(source, c);
    }
    return source;
}

const TestKeeloq = () => {
    const key = '12345678';//密钥
    const available = 15; //次数
    const deviceCode = '123F'; //设备码
    const encryt_flag = 0x75; //标志 01110101
    const random = Math.floor(Math.random()*(16+1));//随机数
    const deviceCodeArray = Array.from(deviceCode).map(i => parseInt(i, 16));//设备码转数值数组[1,2,3,15]
    const source = encryt_flag << 24 
                | deviceCodeArray[0] << 20 | deviceCodeArray[1] << 16 
                | deviceCodeArray[2] << 12 | deviceCodeArray[3] << 8 
                | random << 4 | available
    const s = addChecksum(source);
    const crypt = ENCRYPT(s, key);
    console.log("密钥：" + key + "， 加密明文：" + s);
    console.log("临时密码：" + crypt);
    let message = DECRYPT(crypt, key);
    console.log("密钥: " + key + "， 解密明文：" + message );
    s !== message && console.log("==================================解密结果异常=================")

    const checksum = (message >> 24) & 0xFF;
    const messageArray = int2ByteArray(message);
    messageArray[3] = encryt_flag;
    const myChecksum = messageArray.reduce((a, b) => a + b) & 0xFF;
    if (checksum !== myChecksum) {
        console.log('=============校验失败==========')
    } else {
        const myAvailable = message & 0xF
        console.log('可使用次数：' + myAvailable);
        const myDeviceCode = [(message >> 20) & 0xF, (message >> 16) & 0xF, (message >> 12) & 0xF, (message >> 8) & 0xF];
        console.log('设备码：', myDeviceCode.map(i => (i).toString(16).toUpperCase()).join(''))
    }
}
export {
    ENCRYPT, DECRYPT, TestKeeloq
}